using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using Unity.VisualScripting;

//using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using static HotUpdate;

/// <summary>
/// 挂载在canvas（因为要实例化UI为该物体的子物体）
/// </summary>
public class HotUpdate : MonoBehaviour
{
    //用于存储FileList文件的信息，用于最后写入文件(必须是全局的，否则无法在最后写入的函数中传入）
    //一个用于存储制度目录的；一个用于存储服务器的
    byte[] m_ReadPathFileListData;
    byte[] m_ServerFileListData;

    //用于存储文件地址，文件名和网络下载数据处理
    internal class DownFileInfo
    {
        public string url;
        public string fileName;
        public DownloadHandler fileData;
    }
    //下载文件数量来显示进度(如果ab包比较大，则一般是文件的大小)
    int m_DownloadCount;
    /// <summary>
    /// 下载单个！！文件
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    //协程方法没有返回值，所以需要使用Action委托（回调函数）允许外部代码来传递下载的数据
    IEnumerator DownLoadFile(DownFileInfo downDileInfo, Action<DownFileInfo> Complete)
    {
        //创建一个网络请求
        UnityWebRequest webRequest = UnityWebRequest.Get(downDileInfo.url);
        //发送网络请求，挂起当前协程，等待下载完成
        yield return webRequest.SendWebRequest();
        if(webRequest.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError(webRequest.error);
            yield break;
            //下载失败还可以重试（未写）
        }
        yield return new WaitForSeconds(0.2f);
        //下载完成后,将下载的数据存储进DownFileInfo的fileData属性中
        downDileInfo.fileData = webRequest.downloadHandler;
        //调用回调函数，传入的参数是DownFileInfo类型
        Complete.Invoke(downDileInfo);
        //回调函数执行完毕后，释放资源
        //尽管 webRequest 是协程内部的局部变量，它在协程的生命周期内可用，但它持有的资源并不自动释放。
        //C# 的垃圾回收机制虽然可以处理许多对象，但对于一些非托管资源（如网络连接），需要显式地调用 Dispose() 来释放它们。
        webRequest.Dispose();
    }

    /// <summary>
    /// 下载多个文件
    /// </summary>
    /// <param name="downDileInfos"></param>
    /// <param name="Complete">下载单个文件后的回调参数</param>
    /// <param name="DownLoadAllComplete">下载全部文件后的回调参数</param>
    /// <returns></returns>
    IEnumerator DownLoadFile(List<DownFileInfo> downDileInfos, Action<DownFileInfo> Complete, Action DownLoadAllComplete)
    {
        foreach (DownFileInfo item in downDileInfos)
        {
            yield return DownLoadFile(item, Complete);
        }
        //下载全部文件后的回调，用于通知外部代码下载完成，
        DownLoadAllComplete?.Invoke();
    }

    /// <summary>
    /// 读取filelist文件，获取DownFileInfo文件信息列表
    /// </summary>
    /// <param name="fileData">FileList文件打开后的字符串</param>
    /// <param name="path"></param>
    /// <returns></returns>
    private List<DownFileInfo> GetFileList(string fileData,string path)
    {
        //ReadAllLines(): 直接从文件读取,参数为文件路径
        //当前方法: 接收已经读取的字符串内容，参数为字符串

        //标准化处理：删除字符串的开始和末尾的空白字符，然后替换所有的回车符\r，保证不同行的字符串之间是用换行符\n分隔的
        string content = fileData.Trim().Replace("\r", "");
        string[] files = content.Split('\n');
        List<DownFileInfo> downFileInfos = new List<DownFileInfo>(files.Length);
        for (int i = 0; i < files.Length; i++)
        {
            string[] info = files[i].Split('|');
            DownFileInfo downFileInfo = new DownFileInfo();
            //
            downFileInfo.url = Path.Combine(path, info[1]);
            downFileInfo.fileName = info[1];
            downFileInfos.Add(downFileInfo);
        }
        return downFileInfos;
    }

    GameObject loadingObj;
    LoadingUI loadingUI;
    void Start()
    {
        //loading界面是在更新前加载的，所以是不能更新的，因此只能放在resources下面
        GameObject go = Resources.Load<GameObject>("LoadingUI");
        loadingObj = Instantiate(go);
        loadingObj.transform.SetParent(this.transform);
        loadingUI = loadingObj.GetComponent<LoadingUI>();
        if (IsFirstInstall())
            ReleaseResources();
        else
            CheckUpdate();
    }


    private bool IsFirstInstall()
    {
        //检查只读目录是否存在版本文件
        bool isExistsReadPath = FileUtil.IsExists(Path.Combine(PathUtil.ReadOnlyPath, AppConst.FileListName));
        //检查读写目录是否存在版本文件
        bool isExistsReadWritePath = FileUtil.IsExists(Path.Combine(PathUtil.ReadWritePath, AppConst.FileListName));
        //满足制度目录从存在，读写目录不存在则为首次安装
        return isExistsReadPath && !isExistsReadWritePath;
    }

    /// <summary>
    /// 如果首次安装，需要释放资源到读写目录
    /// </summary>
    private void ReleaseResources()
    {
        m_DownloadCount = 0;
        //获取FileList文件路径
        string url = Path.Combine(PathUtil.ReadOnlyPath, AppConst.FileListName);
        //建立downFileInfo对象，首先存储路径
        DownFileInfo downFileInfo = new DownFileInfo();
        downFileInfo.url = url;
        //启动下载单个文件协程，协程返回FileList的DownFileInfo对象，传入的回调对其进行操作：解析文本，为每个资源建立DownFileInfo对象，返回列表）
        StartCoroutine(DownLoadFile(downFileInfo, OnDownLoadReadPathFileListComplete));
    }

    /// <summary>
    /// 在本地只读目录下载完FileList的回调
    /// 解析文本文件（FileList）为一个列表，并启动下载多个文件的协程，传入单个文件下载完成的回调和全部文件下载完成的回调
    /// </summary>
    /// <param name="downFileInfo">需要解析的FileList文件的DownFileInfo对象</param>
    private void OnDownLoadReadPathFileListComplete(DownFileInfo downFileInfo)
    {
        //对m_ReadPathFileListData赋值存储起来，用于所有资源文件下载后写入FileList
        m_ReadPathFileListData = downFileInfo.fileData.data;
        List<DownFileInfo> downFileInfos = GetFileList(downFileInfo.fileData.text, PathUtil.ReadOnlyPath);
        StartCoroutine(DownLoadFile(downFileInfos, OnReleaseFileComplete, OnReleaseAllFileComplete));
        loadingUI.InitProgress(downFileInfos.Count, "正在释放资源，不消耗流量...");
    }

    /// <summary>
    /// 单个文件下载完成后的操作：写入对应路径
    /// </summary>
    /// <param name="info"></param>
    private void OnReleaseFileComplete(DownFileInfo info)
    {
        Debug.Log("OnReleaseFileComplete:" + info.fileName);
        //文件路径拼接：读写路径+文件名
        string writeFile = Path.Combine(PathUtil.ReadWritePath, info.fileName);
        //字节数组写入（二进制数据）
        FileUtil.WriteFile(writeFile, info.fileData.data);
        m_DownloadCount++;
        loadingUI.UpdateProgress(m_DownloadCount);
    }

    /// <summary>
    /// 所有文件写入后写入FileList文件（最后写入是用于检测是否）
    /// </summary>
    private void OnReleaseAllFileComplete()
    {
        //这里需要把FileList写入目录，
        FileUtil.WriteFile(Path.Combine(PathUtil.ReadWritePath, AppConst.FileListName), m_ReadPathFileListData);
        CheckUpdate();
    }

    private void CheckUpdate()
    {
        string url = Path.Combine(AppConst.ResourcesUrl, AppConst.FileListName);
        DownFileInfo downFileInfo = new DownFileInfo();
        downFileInfo.url = url;
        StartCoroutine(DownLoadFile(downFileInfo, OnDownLoadServerFileListComplete));
    }

    /// <summary>
    /// 在服务器上下载完FileList之后的回调
    /// 不能像第一次安装一样下载所有的文件，否则热更没有意义
    /// </summary>
    /// <param name="info"></param>
    private void OnDownLoadServerFileListComplete(DownFileInfo fileInfo)
    {
        m_DownloadCount = 0;
        m_ServerFileListData = fileInfo.fileData.data;
        List<DownFileInfo> fileInfos = GetFileList(fileInfo.fileData.text, AppConst.ResourcesUrl);
        List<DownFileInfo> downFileInfos = new List<DownFileInfo>();
        for (int i = 0; i < fileInfos.Count; i++)
        {
            string localFile = Path.Combine(PathUtil.ReadWritePath, fileInfos[i].fileName);
            if (!FileUtil.IsExists(localFile))
            {
                fileInfos[i].url = Path.Combine(AppConst.ResourcesUrl, fileInfos[i].fileName);
                downFileInfos.Add(fileInfos[i]);
            }
        }
        if (downFileInfos.Count > 0)
        {
            StartCoroutine(DownLoadFile(downFileInfos, OnUpdataFileComplete, OnUpdateAllFileComplete));
            loadingUI.InitProgress(downFileInfos.Count, "正在更新...");
        }
        else
        {
            EnterGame();
        }
    }
    //和释放资源完成后的回调方法一致（为了增强可读性，用不同的函数名）
    private void OnUpdataFileComplete(DownFileInfo info)
    {
        Debug.Log("OnUpdataFileComplete:" + info.fileName);
        string writeFile = Path.Combine(PathUtil.ReadWritePath, info.fileName);
        FileUtil.WriteFile(writeFile, info.fileData.data);
        m_DownloadCount++;
        loadingUI.UpdateProgress(m_DownloadCount);
    }
    //和释放所有资源完成后的回调方法一致（为了增强可读性，用不同的函数名）
    private void OnUpdateAllFileComplete()
    {
        FileUtil.WriteFile(Path.Combine(PathUtil.ReadWritePath, AppConst.FileListName), m_ServerFileListData);
        EnterGame();
        loadingUI.InitProgress(0, "正在载入...");
    }

    /// <summary>
    /// 最后一步：更新完成进入游戏后执行的逻辑
    /// </summary>
    private void EnterGame()
    {
        Manager.Event.Fire((int)GameEvent.GameInit);
        Destroy(loadingObj);
    }

    /// <summary>
    /// 传入的回调函数：用于加载完ab包中的资源后的操作：实例化、设置位置，父物体，激活状态等
    /// </summary>
    /// <param name="uObject"></param>
    private void Oncomplete(UnityEngine.Object uObject)
    {
        //实例化资源，显式类型转换
        GameObject go = Instantiate(uObject) as GameObject;
        //设置父物体为当前物体
        go.transform.SetParent(this.transform);
        //激活游戏对象
        go.SetActive(true);
        //设置位置
        go.transform.localPosition = Vector3.zero;
    }
}
